package org.dftproject.gedcom.ui.pages;

import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.dftproject.gedcom.core.Gedcom;
import org.dftproject.gedcom.core.GedcomManager;
import org.dftproject.gedcom.core.Line;
import org.dftproject.gedcom.ui.Activator;
import org.dftproject.genesis.ui.pages.AbstractPagePart;
import org.dftproject.genesis.ui.pages.IPageInput;
import org.dftproject.genesis.ui.pages.IPageSite;
import org.dftproject.genesis.ui.pages.PageInitException;
import org.dftproject.genesis.ui.pages.PageManager;
import org.dftproject.genesis.ui.widgets.gallery.Gallery;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.Document;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IRegion;
import org.eclipse.jface.text.ITextViewer;
import org.eclipse.jface.text.Position;
import org.eclipse.jface.text.Region;
import org.eclipse.jface.text.hyperlink.DefaultHyperlinkPresenter;
import org.eclipse.jface.text.hyperlink.IHyperlink;
import org.eclipse.jface.text.hyperlink.IHyperlinkDetector;
import org.eclipse.jface.text.source.SourceViewer;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.LineStyleEvent;
import org.eclipse.swt.custom.LineStyleListener;
import org.eclipse.swt.custom.StyleRange;
import org.eclipse.swt.custom.StyledText;
import org.eclipse.swt.events.DisposeEvent;
import org.eclipse.swt.events.DisposeListener;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.eclipse.ui.IWorkbench;
import org.eclipse.ui.IWorkbenchPage;
import org.eclipse.ui.IWorkbenchWindow;
import org.eclipse.ui.PlatformUI;

public class GedcomSourcePage extends AbstractPagePart {

	public static final String ID = GedcomSourcePage.class.getName();

	private static final Pattern linePattern = Pattern.compile("\\s*(?:(@[^@]+@)\\s+)?([^\\s]+)(?:\\s+(@[^@]+@))?(?:\\s+(.*))?");

	protected final static int VERTICAL_RULER_WIDTH = 12;

	private Color hyperlinkColor;
	private SourceViewer sourceViewer;
	
	private final Map<String, Position> index = new HashMap<String, Position>();

	public void init(IPageSite site, IPageInput input) throws PageInitException {
		if (!(input instanceof GedcomSourcePageInput))
			throw new PageInitException("input must be a " + GedcomSourcePageInput.class.getName());
		setInput(input);
		setSite(site);
	}

	@Override
	public void createPartControl(Composite parent) {
		Composite container = new Composite(parent, SWT.NONE);
		GridLayout layout = new GridLayout();
		layout.marginWidth = layout.marginHeight = layout.marginTop = layout.marginRight = layout.marginBottom = layout.marginLeft = layout.horizontalSpacing = 0;
		layout.numColumns = 2;
		container.setLayout(layout);

		createGallery(container);

		Composite body = new Composite(container, SWT.NONE);
		layout = new GridLayout();
		layout.marginWidth = layout.marginHeight = layout.verticalSpacing = layout.marginTop = layout.marginRight = layout.marginBottom = layout.verticalSpacing = 0;
		layout.marginLeft = 5;
		body.setLayout(layout);
		body.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
		
		Document doc = new Document();
		doc.set("");

		sourceViewer = new SourceViewer(body, null, null, true, SWT.MULTI | SWT.V_SCROLL | SWT.H_SCROLL);
		sourceViewer.getControl().setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
		sourceViewer.setDocument(doc);
		sourceViewer.setEditable(false);
		
		//sourceViewer.getTextWidget().setFont(JFaceResources.getFont(JFaceResources.TEXT_FONT));
		
		DefaultHyperlinkPresenter hyperlinkPresenter = new DefaultHyperlinkPresenter(hyperlinkColor = new Color(parent.getDisplay(), 0, 0, 255));
		sourceViewer.setHyperlinkPresenter(hyperlinkPresenter);
		sourceViewer.setHyperlinkDetectors(new IHyperlinkDetector[] { new RecordHyperlinkDetector() }, SWT.CTRL);

		final GedcomLineStyleListener lineStyleListener = new GedcomLineStyleListener();

		final StyledText textWidget = sourceViewer.getTextWidget();
		textWidget.addLineStyleListener(lineStyleListener);
		textWidget.addDisposeListener(new DisposeListener() {
			public void widgetDisposed(DisposeEvent e) {
				textWidget.removeLineStyleListener(lineStyleListener);
			}
		});

		Job job = new Job("Load GEDCOM") {

			@Override
			protected IStatus run(IProgressMonitor monitor) {
				Gedcom gedcom = GedcomManager.getDefault().getGedcom(((GedcomSourcePageInput) getInput()).getFilename());
				
				final StringBuilder sb = new StringBuilder();
				
				List<Line> lines = gedcom.getLines();
				Iterator<Line> iter = lines.iterator();
				while (iter.hasNext()) {
					appendLine(iter.next(), sb);
					if (iter.hasNext())
						sb.append("\n");
				}

				Display.getDefault().asyncExec(new Runnable() {

					public void run() {
						sourceViewer.getDocument().set(sb.toString());
					}

				});

				return Status.OK_STATUS;
			}

		};
		job.schedule();
	}
	
	protected void createGallery(Composite parent) {
		Gallery gallery = new Gallery(parent);
		gallery.setLayoutData(new GridData(SWT.LEFT, SWT.FILL, false, true));

		gallery.addItem(Activator.getImageDescriptor("icons/page.png").createImage(), false, "Individuals", new Runnable() {

			public void run() {
				IWorkbench workbench = PlatformUI.getWorkbench();
				IWorkbenchWindow window = workbench.getActiveWorkbenchWindow();
				IWorkbenchPage workbenchPage = window.getActivePage();
				PageManager.getDefault().openPage(workbenchPage, new GedcomPageInput(((GedcomSourcePageInput) getInput()).getFilename()), GedcomPage.ID, true, false);
			}

		});
		gallery.addItem(Activator.getImageDescriptor("icons/page_white_text.png").createImage(), true, "Source", null);
	}

	@Override
	public void setFocus() {
		sourceViewer.getControl().setFocus();
	}

	@Override
	public void dispose() {
		if (hyperlinkColor != null) {
			hyperlinkColor.dispose();
			hyperlinkColor = null;
		}

		super.dispose();
	}

	protected void appendLine(Line line, StringBuilder sb) {
		if (line.getId() != null)
			index.put(line.getId(), new Position(sb.length()));
		
		sb.append(line.getTabbedString());

		List<Line> children = line.getChildren();
		Iterator<Line> iter = children.iterator();
		while (iter.hasNext()) {
			sb.append("\n");
			appendLine(iter.next(), sb);
		}
	}

	protected class GedcomLineStyleListener implements LineStyleListener {

		private final Color xref;
		private final Color tag;

		public GedcomLineStyleListener() {
			xref = new Color(Display.getDefault(), 127, 0, 0);
			tag = new Color(Display.getDefault(), 0, 0, 127);
		}

		public void lineGetStyle(LineStyleEvent event) {
			List<StyleRange> styles = new LinkedList<StyleRange>();

			Matcher m = linePattern.matcher(event.lineText);
			while (m.find()) {
				if (m.group(1) != null)
					styles.add(new StyleRange(event.lineOffset + m.start(1), m.end(1) - m.start(1), xref, null, SWT.BOLD));
				if (m.group(2) != null)
					styles.add(new StyleRange(event.lineOffset + m.start(2), m.end(2) - m.start(2), tag, null));
				if (m.group(3) != null)
					styles.add(new StyleRange(event.lineOffset + m.start(3), m.end(3) - m.start(3), xref, null));
			}

			event.styles = styles.toArray(new StyleRange[0]);
		}

		public void dispose() {
			tag.dispose();
			xref.dispose();
		}

	};
	
	protected class RecordHyperlinkDetector implements IHyperlinkDetector {

		public IHyperlink[] detectHyperlinks(ITextViewer textViewer, IRegion region, boolean canShowMultipleHyperlinks) {
			IDocument doc = textViewer.getDocument();

			int offset = region.getOffset();

			final IRegion lineInfo;
			String line;
			try {
				lineInfo = doc.getLineInformationOfOffset(offset);
				line = doc.get(lineInfo.getOffset(), lineInfo.getLength());
			} catch (BadLocationException ex) {
				return null;
			}
			
			int offsetIntoLine = offset - lineInfo.getOffset();
			
			if (line != null) {
				final Matcher matcher = linePattern.matcher(line);
				if (matcher.matches()) {
					if (matcher.group(3) != null) {
						if (matcher.start(3) <= offsetIntoLine && matcher.end(3) >= offsetIntoLine) {
							final String xref = matcher.group(3);
							final Position position = index.get(xref.substring(1, xref.length() - 1));
							
							return new IHyperlink[] {
									new IHyperlink() {

										public IRegion getHyperlinkRegion() {
											return new Region(lineInfo.getOffset() + matcher.start(3), matcher.end(3) - matcher.start(3));
										}

										public String getHyperlinkText() {
											return null;
										}

										public String getTypeLabel() {
											return null;
										}

										public void open() {
											sourceViewer.setSelectedRange(position.getOffset(), xref.length());
											sourceViewer.revealRange(position.getOffset(), xref.length());
										}
										
									}
								};
						}
					}
				}
			}
			
			return null;
		}

	}

}
